package feizhou.untils.tools;

import java.io.PrintWriter;

/**
 * 矩形区域范围。
 */
public class RectD {
    
    public double left;
    
    public double top;
    
    public double right;
    
    public double bottom;
    
    /**
     * Create a new empty RectD. All coordinates are initialized to 0.
     */
    public RectD() {
    }
    
    /**
     * Create a new rectangle with the specified coordinates. Note: no range checking is performed, so the caller must
     * ensure that left <= right and top <= bottom.
     *
     * @param left The X coordinate of the left side of the rectangle
     * @param top The Y coordinate of the top of the rectangle
     * @param right The X coordinate of the right side of the rectangle
     * @param bottom The Y coordinate of the bottom of the rectangle
     */
    public RectD(double left, double top, double right, double bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }
    
    /**
     * Create a new rectangle, initialized with the values in the specified rectangle (which is left unmodified).
     *
     * @param r The rectangle whose coordinates are copied into the new rectangle.
     */
    public RectD(RectD r) {
        if (r == null) {
            left = top = right = bottom = 0.0f;
        }
        else {
            left = r.left;
            top = r.top;
            right = r.right;
            bottom = r.bottom;
        }
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        
        RectD r = (RectD)o;
        return left == r.left && top == r.top && right == r.right && bottom == r.bottom;
    }
    
    @Override
    public String toString() {
        return "RectD(" + left + ", " + top + ", " + right + ", " + bottom + ")";
    }
    
    /**
     * Return a string representation of the rectangle in a compact form.
     */
    public String toShortString() {
        return toShortString(new StringBuilder(32));
    }
    
    /**
     * Return a string representation of the rectangle in a compact form.
     * 
     * @hide
     */
    public String toShortString(StringBuilder sb) {
        sb.setLength(0);
        sb.append('[');
        sb.append(left);
        sb.append(',');
        sb.append(top);
        sb.append("][");
        sb.append(right);
        sb.append(',');
        sb.append(bottom);
        sb.append(']');
        return sb.toString();
    }
    
    /**
     * Print short representation to given writer.
     * 
     * @hide
     */
    public void printShortString(PrintWriter pw) {
        pw.print('[');
        pw.print(left);
        pw.print(',');
        pw.print(top);
        pw.print("][");
        pw.print(right);
        pw.print(',');
        pw.print(bottom);
        pw.print(']');
    }
    
    /**
     * Returns true if the rectangle is empty (left >= right or top >= bottom)
     */
    public final boolean isEmpty() {
        return left >= right || top >= bottom;
    }
    
    /**
     * @return the rectangle's width. This does not check for a valid rectangle (i.e. left <= right) so the result may
     *         be negative.
     */
    public final double width() {
        return right - left;
    }
    
    /**
     * @return the rectangle's height. This does not check for a valid rectangle (i.e. top <= bottom) so the result may
     *         be negative.
     */
    public final double height() {
        return bottom - top;
    }
    
    /**
     * @return the horizontal center of the rectangle. This does not check for a valid rectangle (i.e. left <= right)
     */
    public final double centerX() {
        return (left + right) * 0.5f;
    }
    
    /**
     * @return the vertical center of the rectangle. This does not check for a valid rectangle (i.e. top <= bottom)
     */
    public final double centerY() {
        return (top + bottom) * 0.5f;
    }
    
    /**
     * Set the rectangle to (0,0,0,0)
     */
    public void setEmpty() {
        left = right = top = bottom = 0;
    }
    
    /**
     * Set the rectangle's coordinates to the specified values. Note: no range checking is performed, so it is up to the
     * caller to ensure that left <= right and top <= bottom.
     *
     * @param left The X coordinate of the left side of the rectangle
     * @param top The Y coordinate of the top of the rectangle
     * @param right The X coordinate of the right side of the rectangle
     * @param bottom The Y coordinate of the bottom of the rectangle
     */
    public void set(double left, double top, double right, double bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }
    
    /**
     * Copy the coordinates from src into this rectangle.
     *
     * @param src The rectangle whose coordinates are copied into this rectangle.
     */
    public void set(RectD src) {
        this.left = src.left;
        this.top = src.top;
        this.right = src.right;
        this.bottom = src.bottom;
    }
    
    /**
     * Offset the rectangle by adding dx to its left and right coordinates, and adding dy to its top and bottom
     * coordinates.
     *
     * @param dx The amount to add to the rectangle's left and right coordinates
     * @param dy The amount to add to the rectangle's top and bottom coordinates
     */
    public void offset(double dx, double dy) {
        left += dx;
        top += dy;
        right += dx;
        bottom += dy;
    }
    
    /**
     * Offset the rectangle to a specific (left, top) position, keeping its width and height the same.
     *
     * @param newLeft The new "left" coordinate for the rectangle
     * @param newTop The new "top" coordinate for the rectangle
     */
    public void offsetTo(double newLeft, double newTop) {
        right += newLeft - left;
        bottom += newTop - top;
        left = newLeft;
        top = newTop;
    }
    
    /**
     * Inset the rectangle by (dx,dy). If dx is positive, then the sides are moved inwards, making the rectangle
     * narrower. If dx is negative, then the sides are moved outwards, making the rectangle wider. The same holds true
     * for dy and the top and bottom.
     *
     * @param dx The amount to add(subtract) from the rectangle's left(right)
     * @param dy The amount to add(subtract) from the rectangle's top(bottom)
     */
    public void inset(double dx, double dy) {
        left += dx;
        top += dy;
        right -= dx;
        bottom -= dy;
    }
    
    /**
     * Returns true if (x,y) is inside the rectangle. The left and top are considered to be inside, while the right and
     * bottom are not. This means that for a x,y to be contained: left <= x < right and top <= y < bottom. An empty
     * rectangle never contains any point.
     *
     * @param x The X coordinate of the point being tested for containment
     * @param y The Y coordinate of the point being tested for containment
     * @return true iff (x,y) are contained by the rectangle, where containment means left <= x < right and top <= y <
     *         bottom
     */
    public boolean contains(double x, double y) {
        return left < right && top < bottom // check for empty first
            && x >= left && x < right && y >= top && y < bottom;
    }
    
    /**
     * Returns true iff the 4 specified sides of a rectangle are inside or equal to this rectangle. i.e. is this
     * rectangle a superset of the specified rectangle. An empty rectangle never contains another rectangle.
     *
     * @param left The left side of the rectangle being tested for containment
     * @param top The top of the rectangle being tested for containment
     * @param right The right side of the rectangle being tested for containment
     * @param bottom The bottom of the rectangle being tested for containment
     * @return true iff the the 4 specified sides of a rectangle are inside or equal to this rectangle
     */
    public boolean contains(double left, double top, double right, double bottom) {
        // check for empty first
        return this.left < this.right && this.top < this.bottom
        // now check for containment
            && this.left <= left && this.top <= top && this.right >= right && this.bottom >= bottom;
    }
    
    /**
     * Returns true iff the specified rectangle r is inside or equal to this rectangle. An empty rectangle never
     * contains another rectangle.
     *
     * @param r The rectangle being tested for containment.
     * @return true iff the specified rectangle r is inside or equal to this rectangle
     */
    public boolean contains(RectD r) {
        // check for empty first
        return this.left < this.right && this.top < this.bottom
        // now check for containment
            && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom;
    }
    
    /**
     * If the rectangle specified by left,top,right,bottom intersects this rectangle, return true and set this rectangle
     * to that intersection, otherwise return false and do not change this rectangle. No check is performed to see if
     * either rectangle is empty. Note: To just test for intersection, use intersects()
     *
     * @param left The left side of the rectangle being intersected with this rectangle
     * @param top The top of the rectangle being intersected with this rectangle
     * @param right The right side of the rectangle being intersected with this rectangle.
     * @param bottom The bottom of the rectangle being intersected with this rectangle.
     * @return true if the specified rectangle and this rectangle intersect (and this rectangle is then set to that
     *         intersection) else return false and do not change this rectangle.
     */
    public boolean intersect(double left, double top, double right, double bottom) {
        if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
            if (this.left < left) {
                this.left = left;
            }
            if (this.top < top) {
                this.top = top;
            }
            if (this.right > right) {
                this.right = right;
            }
            if (this.bottom > bottom) {
                this.bottom = bottom;
            }
            return true;
        }
        return false;
    }
    
    /**
     * If the specified rectangle intersects this rectangle, return true and set this rectangle to that intersection,
     * otherwise return false and do not change this rectangle. No check is performed to see if either rectangle is
     * empty. To just test for intersection, use intersects()
     *
     * @param r The rectangle being intersected with this rectangle.
     * @return true if the specified rectangle and this rectangle intersect (and this rectangle is then set to that
     *         intersection) else return false and do not change this rectangle.
     */
    public boolean intersect(RectD r) {
        return intersect(r.left, r.top, r.right, r.bottom);
    }
    
    /**
     * If rectangles a and b intersect, return true and set this rectangle to that intersection, otherwise return false
     * and do not change this rectangle. No check is performed to see if either rectangle is empty. To just test for
     * intersection, use intersects()
     *
     * @param a The first rectangle being intersected with
     * @param b The second rectangle being intersected with
     * @return true iff the two specified rectangles intersect. If they do, set this rectangle to that intersection. If
     *         they do not, return false and do not change this rectangle.
     */
    public boolean setIntersect(RectD a, RectD b) {
        if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) {
            left = Math.max(a.left, b.left);
            top = Math.max(a.top, b.top);
            right = Math.min(a.right, b.right);
            bottom = Math.min(a.bottom, b.bottom);
            return true;
        }
        return false;
    }
    
    /**
     * Returns true if this rectangle intersects the specified rectangle. In no event is this rectangle modified. No
     * check is performed to see if either rectangle is empty. To record the intersection, use intersect() or
     * setIntersect().
     *
     * @param left The left side of the rectangle being tested for intersection
     * @param top The top of the rectangle being tested for intersection
     * @param right The right side of the rectangle being tested for intersection
     * @param bottom The bottom of the rectangle being tested for intersection
     * @return true iff the specified rectangle intersects this rectangle. In no event is this rectangle modified.
     */
    public boolean intersects(double left, double top, double right, double bottom) {
        return this.left < right && left < this.right && this.top < bottom && top < this.bottom;
    }
    
    /**
     * Returns true iff the two specified rectangles intersect. In no event are either of the rectangles modified. To
     * record the intersection, use intersect() or setIntersect().
     *
     * @param a The first rectangle being tested for intersection
     * @param b The second rectangle being tested for intersection
     * @return true iff the two specified rectangles intersect. In no event are either of the rectangles modified.
     */
    public static boolean intersects(RectD a, RectD b) {
        return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
    }
    
    /**
     * Update this Rect to enclose itself and the specified rectangle. If the specified rectangle is empty, nothing is
     * done. If this rectangle is empty it is set to the specified rectangle.
     *
     * @param left The left edge being unioned with this rectangle
     * @param top The top edge being unioned with this rectangle
     * @param right The right edge being unioned with this rectangle
     * @param bottom The bottom edge being unioned with this rectangle
     */
    public void union(double left, double top, double right, double bottom) {
        if ((left < right) && (top < bottom)) {
            if ((this.left < this.right) && (this.top < this.bottom)) {
                if (this.left > left)
                    this.left = left;
                if (this.top > top)
                    this.top = top;
                if (this.right < right)
                    this.right = right;
                if (this.bottom < bottom)
                    this.bottom = bottom;
            }
            else {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }
        }
    }
    
    /**
     * Update this Rect to enclose itself and the specified rectangle. If the specified rectangle is empty, nothing is
     * done. If this rectangle is empty it is set to the specified rectangle.
     *
     * @param r The rectangle being unioned with this rectangle
     */
    public void union(RectD r) {
        union(r.left, r.top, r.right, r.bottom);
    }
    
    /**
     * Update this Rect to enclose itself and the [x,y] coordinate. There is no check to see that this rectangle is
     * non-empty.
     *
     * @param x The x coordinate of the point to add to the rectangle
     * @param y The y coordinate of the point to add to the rectangle
     */
    public void union(double x, double y) {
        if (x < left) {
            left = x;
        }
        else if (x > right) {
            right = x;
        }
        if (y < top) {
            top = y;
        }
        else if (y > bottom) {
            bottom = y;
        }
    }
    
    /**
     * Swap top/bottom or left/right if there are flipped (i.e. left > right and/or top > bottom). This can be called if
     * the edges are computed separately, and may have crossed over each other. If the edges are already correct (i.e.
     * left <= right and top <= bottom) then nothing is done.
     */
    public void sort() {
        if (left > right) {
            double temp = left;
            left = right;
            right = temp;
        }
        if (top > bottom) {
            double temp = top;
            top = bottom;
            bottom = temp;
        }
    }
    
}
